<!DOCTYPE HTML>
<html lang="zh-CN">


<head>
    <meta charset="utf-8">
    <meta name="keywords" content="冰冰要我教她 undo log，怎么办, Gtwff">
    <meta name="description" content="">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
    <meta name="renderer" content="webkit|ie-stand|ie-comp">
    <meta name="mobile-web-app-capable" content="yes">
    <meta name="format-detection" content="telephone=no">
    <meta name="apple-mobile-web-app-capable" content="yes">
    <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
    <meta name="referrer" content="no-referrer-when-downgrade">
    <!-- Global site tag (gtag.js) - Google Analytics -->

<script async src="https://www.googletagmanager.com/gtag/js?id="></script>
<script>
    window.dataLayer = window.dataLayer || [];
    function gtag() {
        dataLayer.push(arguments);
    }

    gtag('js', new Date());
    gtag('config', '');
</script>


    <title>冰冰要我教她 undo log，怎么办 | Gtwff</title>
    <link rel="icon" type="image/png" href="/favicon.png">

    <link rel="stylesheet" type="text/css" href="/libs/awesome/css/all.css">
    <link rel="stylesheet" type="text/css" href="/libs/materialize/materialize.min.css">
    <link rel="stylesheet" type="text/css" href="/libs/aos/aos.css">
    <link rel="stylesheet" type="text/css" href="/libs/animate/animate.min.css">
    <link rel="stylesheet" type="text/css" href="/libs/lightGallery/css/lightgallery.min.css">
    <link rel="stylesheet" type="text/css" href="/css/matery.css">
    <link rel="stylesheet" type="text/css" href="/css/my.css">

    <script src="/libs/jquery/jquery.min.js"></script>

<meta name="generator" content="Hexo 5.4.0"></head>



   <style>
    body{
       background-image: url(https://cdn.jsdelivr.net/gh/Tokisaki-Galaxy/res/site/medias/background.jpg);
       background-repeat:no-repeat;
       background-size:cover;
       background-attachment:fixed;
    }
</style>



<body>
    <header class="navbar-fixed">
    <nav id="headNav" class="bg-color nav-transparent">
        <div id="navContainer" class="nav-wrapper container">
            <div class="brand-logo">
                <a href="/" class="waves-effect waves-light">
                    
                    <img src="/medias/logo.png" class="logo-img" alt="LOGO">
                    
                    <span class="logo-span">Gtwff</span>
                </a>
            </div>
            

<a href="#" data-target="mobile-nav" class="sidenav-trigger button-collapse"><i class="fas fa-bars"></i></a>
<ul class="right nav-menu">
  
  <li class="hide-on-med-and-down nav-item">
    
    <a href="/" class="waves-effect waves-light">
      
      <i class="fas fa-home" style="zoom: 0.6;"></i>
      
      <span>首页</span>
    </a>
    
  </li>
  
  <li class="hide-on-med-and-down nav-item">
    
    <a href="/tags" class="waves-effect waves-light">
      
      <i class="fas fa-tags" style="zoom: 0.6;"></i>
      
      <span>标签</span>
    </a>
    
  </li>
  
  <li class="hide-on-med-and-down nav-item">
    
    <a href="/categories" class="waves-effect waves-light">
      
      <i class="fas fa-bookmark" style="zoom: 0.6;"></i>
      
      <span>分类</span>
    </a>
    
  </li>
  
  <li class="hide-on-med-and-down nav-item">
    
    <a href="/archives" class="waves-effect waves-light">
      
      <i class="fas fa-archive" style="zoom: 0.6;"></i>
      
      <span>归档</span>
    </a>
    
  </li>
  
  <li class="hide-on-med-and-down nav-item">
    
    <a href="/about" class="waves-effect waves-light">
      
      <i class="fas fa-user-circle" style="zoom: 0.6;"></i>
      
      <span>关于</span>
    </a>
    
  </li>
  
  <li class="hide-on-med-and-down nav-item">
    
    <a href="/contact" class="waves-effect waves-light">
      
      <i class="fas fa-comments" style="zoom: 0.6;"></i>
      
      <span>留言板</span>
    </a>
    
  </li>
  
  <li class="hide-on-med-and-down nav-item">
    
    <a href="/friends" class="waves-effect waves-light">
      
      <i class="fas fa-address-book" style="zoom: 0.6;"></i>
      
      <span>友情链接</span>
    </a>
    
  </li>
  
  <li>
    <a href="#searchModal" class="modal-trigger waves-effect waves-light">
      <i id="searchIcon" class="fas fa-search" title="搜索" style="zoom: 0.85;"></i>
    </a>
  </li>
</ul>


<div id="mobile-nav" class="side-nav sidenav">

    <div class="mobile-head bg-color">
        
        <img src="/medias/logo.png" class="logo-img circle responsive-img">
        
        <div class="logo-name">Gtwff</div>
        <div class="logo-desc">
            
            Never really desperate, only the lost of the soul.
            
        </div>
    </div>

    <ul class="menu-list mobile-menu-list">
        
        <li class="m-nav-item">
	  
		<a href="/" class="waves-effect waves-light">
			
			    <i class="fa-fw fas fa-home"></i>
			
			首页
		</a>
          
        </li>
        
        <li class="m-nav-item">
	  
		<a href="/tags" class="waves-effect waves-light">
			
			    <i class="fa-fw fas fa-tags"></i>
			
			标签
		</a>
          
        </li>
        
        <li class="m-nav-item">
	  
		<a href="/categories" class="waves-effect waves-light">
			
			    <i class="fa-fw fas fa-bookmark"></i>
			
			分类
		</a>
          
        </li>
        
        <li class="m-nav-item">
	  
		<a href="/archives" class="waves-effect waves-light">
			
			    <i class="fa-fw fas fa-archive"></i>
			
			归档
		</a>
          
        </li>
        
        <li class="m-nav-item">
	  
		<a href="/about" class="waves-effect waves-light">
			
			    <i class="fa-fw fas fa-user-circle"></i>
			
			关于
		</a>
          
        </li>
        
        <li class="m-nav-item">
	  
		<a href="/contact" class="waves-effect waves-light">
			
			    <i class="fa-fw fas fa-comments"></i>
			
			留言板
		</a>
          
        </li>
        
        <li class="m-nav-item">
	  
		<a href="/friends" class="waves-effect waves-light">
			
			    <i class="fa-fw fas fa-address-book"></i>
			
			友情链接
		</a>
          
        </li>
        
        
        <li><div class="divider"></div></li>
        <li>
            <a href="https://gitee.com/kuangty/kuangty" class="waves-effect waves-light" target="_blank">
                <i class="fab fa-github-square fa-fw"></i>Fork Me
            </a>
        </li>
        
    </ul>
</div>


        </div>

        
            <style>
    .nav-transparent .github-corner {
        display: none !important;
    }

    .github-corner {
        position: absolute;
        z-index: 10;
        top: 0;
        right: 0;
        border: 0;
        transform: scale(1.1);
    }

    .github-corner svg {
        color: #0f9d58;
        fill: #fff;
        height: 64px;
        width: 64px;
    }

    .github-corner:hover .octo-arm {
        animation: a 0.56s ease-in-out;
    }

    .github-corner .octo-arm {
        animation: none;
    }

    @keyframes a {
        0%,
        to {
            transform: rotate(0);
        }
        20%,
        60% {
            transform: rotate(-25deg);
        }
        40%,
        80% {
            transform: rotate(10deg);
        }
    }
</style>

<a href="https://gitee.com/kuangty/kuangty" class="github-corner tooltipped hide-on-med-and-down" target="_blank"
   data-tooltip="Fork Me" data-position="left" data-delay="50">
    <svg viewBox="0 0 250 250" aria-hidden="true">
        <path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path>
        <path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2"
              fill="currentColor" style="transform-origin: 130px 106px;" class="octo-arm"></path>
        <path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z"
              fill="currentColor" class="octo-body"></path>
    </svg>
</a>
        
    </nav>

</header>

    



<div class="bg-cover pd-header post-cover" style="background-image: url('/medias/featureimages/0.jpg')">
    <div class="container" style="right: 0px;left: 0px;">
        <div class="row">
            <div class="col s12 m12 l12">
                <div class="brand">
                    <h1 class="description center-align post-title">冰冰要我教她 undo log，怎么办</h1>
                </div>
            </div>
        </div>
    </div>
</div>




<main class="post-container content">

    
    <link rel="stylesheet" href="/libs/tocbot/tocbot.css">
<style>
    #articleContent h1::before,
    #articleContent h2::before,
    #articleContent h3::before,
    #articleContent h4::before,
    #articleContent h5::before,
    #articleContent h6::before {
        display: block;
        content: " ";
        height: 100px;
        margin-top: -100px;
        visibility: hidden;
    }

    #articleContent :focus {
        outline: none;
    }

    .toc-fixed {
        position: fixed;
        top: 64px;
    }

    .toc-widget {
        width: 345px;
        padding-left: 20px;
    }

    .toc-widget .toc-title {
        padding: 35px 0 15px 17px;
        font-size: 1.5rem;
        font-weight: bold;
        line-height: 1.5rem;
    }

    .toc-widget ol {
        padding: 0;
        list-style: none;
    }

    #toc-content {
        padding-bottom: 30px;
        overflow: auto;
    }

    #toc-content ol {
        padding-left: 10px;
    }

    #toc-content ol li {
        padding-left: 10px;
    }

    #toc-content .toc-link:hover {
        color: #42b983;
        font-weight: 700;
        text-decoration: underline;
    }

    #toc-content .toc-link::before {
        background-color: transparent;
        max-height: 25px;

        position: absolute;
        right: 23.5vw;
        display: block;
    }

    #toc-content .is-active-link {
        color: #42b983;
    }

    #floating-toc-btn {
        position: fixed;
        right: 15px;
        bottom: 76px;
        padding-top: 15px;
        margin-bottom: 0;
        z-index: 998;
    }

    #floating-toc-btn .btn-floating {
        width: 48px;
        height: 48px;
    }

    #floating-toc-btn .btn-floating i {
        line-height: 48px;
        font-size: 1.4rem;
    }
</style>
<div class="row">
    <div id="main-content" class="col s12 m12 l9">
        <!-- 文章内容详情 -->
<div id="artDetail">
    <div class="card">
        <div class="card-content article-info">
            <div class="row tag-cate">
                <div class="col s7">
                    
                    <div class="article-tag">
                        
                            <a href="/tags/undo-log/">
                                <span class="chip bg-color">undo log</span>
                            </a>
                        
                    </div>
                    
                </div>
                <div class="col s5 right-align">
                    
                    <div class="post-cate">
                        <i class="fas fa-bookmark fa-fw icon-category"></i>
                        
                            <a href="/categories/MySQL/" class="post-category">
                                MySQL
                            </a>
                        
                    </div>
                    
                </div>
            </div>

            <div class="post-info">
                
                <div class="post-date info-break-policy">
                    <i class="far fa-calendar-minus fa-fw"></i>发布日期:&nbsp;&nbsp;
                    2021-11-04
                </div>
                

                
                <div class="post-date info-break-policy">
                    <i class="far fa-calendar-check fa-fw"></i>更新日期:&nbsp;&nbsp;
                    2022-03-27
                </div>
                

                
                <div class="info-break-policy">
                    <i class="far fa-file-word fa-fw"></i>文章字数:&nbsp;&nbsp;
                    5.9k
                </div>
                

                
                <div class="info-break-policy">
                    <i class="far fa-clock fa-fw"></i>阅读时长:&nbsp;&nbsp;
                    21 分
                </div>
                

                
                    <div id="busuanzi_container_page_pv" class="info-break-policy">
                        <i class="far fa-eye fa-fw"></i>阅读次数:&nbsp;&nbsp;
                        <span id="busuanzi_value_page_pv"></span>
                    </div>
				
            </div>
        </div>
        <hr class="clearfix">

        
        <!-- 是否加载使用自带的 prismjs. -->
        <link rel="stylesheet" href="/libs/prism/prism.css">
        

        
        <!-- 代码块折行 -->
        <style type="text/css">
            code[class*="language-"], pre[class*="language-"] { white-space: pre-wrap !important; }
        </style>
        

        <div class="card-content article-card-content">
            <div id="articleContent">
                <h2 id="一、事务回滚的需求"><a href="#一、事务回滚的需求" class="headerlink" title="一、事务回滚的需求"></a>一、事务回滚的需求</h2><p>我们说过事务需要保证原子性 ，也就是事务中的操作要么全部完成，要么什么也不做。但是偏偏有时候事务执行到一半会出现一些情况，比如：</p>
<ul>
<li>情况一：事务执行过程中可能遇到各种错误，比如服务器本身的错误，操作系统错误，甚至是突然断电导致的错误。</li>
<li>情况二：程序员可以在事务执行过程中手动输入 ROLLBACK 语句结束当前的事务的执行。</li>
</ul>
<p>这两种情况都会导致事务执行到一半就结束，但是事务执行过程中可能已经修改了很多东西，为了保证事务的原子性，我们需要把东西改回原先的样子，这个过程就称之为 回滚 （英文名： rollback ）</p>
<h2 id="二、事务-id"><a href="#二、事务-id" class="headerlink" title="二、事务 id"></a>二、事务 id</h2><p><strong>给事务分配id的时机：</strong></p>
<p>一个事务可以是一个只读事务，或者是一个读写事务：  </p>
<ul>
<li>我们可以通过 START TRANSACTION READ ONLY 语句开启一个只读事务。<br>  在只读事务中不可以对普通的表（其他事务也能访问到的表）进行增、删、改操作，但可以对临时表做增、删、改操作。  </li>
<li>我们可以通过 START TRANSACTION READ WRITE 语句开启一个读写事务，或者使用 BEGIN 、 START TRANSACTION 语句开启的事务默认也算是读写事务。在读写事务中可以对表执行增删改查操作  </li>
</ul>
<p>如果某个事务执行过程中对某个表执行了增、删、改操作，那么 InnoDB 存储引擎就会给它分配一个独一无二的事务id ，分配方式如下：  </p>
<ul>
<li><p>对于只读事务来说，只有在它第一次对某个用户创建的临时表执行增、删、改操作时才会为这个事务分配一个 事务id ，否则的话是不分配 事务id 的  </p>
</li>
<li><p>对于读写事务来说，只有在它第一次对某个表（包括用户创建的临时表）执行增、删、改操作时才会为这个事务分配一个 事务id ，否则的话也是不分配 事务id 的。</p>
</li>
</ul>
<p>有的时候虽然我们开启了一个读写事务，但是在这个事务中全是查询语句，并没有执行增、删、改的语句，那也就意味着这个事务并不会被分配一个 事务id  </p>
<blockquote>
<p>小贴士：事务对表中的记录做改动时才会为这个事务分配一个唯一的 事务id  </p>
</blockquote>
<p>以上我们讲了在哪种情况下事务才有事务id，那这个事务id是如何生成的呢？🤭🤭🤭</p>
<p><strong>事务id是怎么生成的：</strong></p>
<p>本质就是一个数字。</p>
<p>服务器会在内存中维护一个全局变量，每当需要为某个事务分配一个 事务id 时，就会把该变量的值当作事务id 分配给该事务，并且把该变量自增1。</p>
<p>每当这个变量的值为 256 的倍数时，就会将该变量的值刷新到系统表空间的页号为 5 的页面中一个称之为<br>Max Trx ID 的属性处，这个属性占用 8 个字节的存储空间。</p>
<p>当系统下一次重新启动时，会将上边提到的 Max Trx ID 属性加载到内存中，将该值加上256之后赋值给我们前边提到的全局变量</p>
<p>这样就可以保证整个系统中分配的事务id值是一个递增的数字。先被分配 id 的事务得到的是较小的事务id,后被分配 id 的事务得到的是较大的 事务id 。  </p>
<p><strong>trx_id隐藏列：</strong></p>
<p>聚簇索引的记录除了会保存完整的用户数据以外，而且还会自动添加名为 <code>trx_id</code>、<code>roll_pointer</code> 的隐藏列，如果用户没有在表中定义主键以及 UNIQUE键，还会自动添加一个名为 row_id 的隐藏列。所以一条记录在页面中的真实结构看起来就是这样的：  </p>
<img src="https://gitee.com/kuangty/blogImage/raw/master/img/2021-11-02_18-52-05.png" style="zoom:80%;" />

<h2 id="三、undo日志的格式"><a href="#三、undo日志的格式" class="headerlink" title="三、undo日志的格式"></a>三、undo日志的格式</h2><p>为了实现事务的 原子性 ，InnoDB 存储引擎在实际进行增、删、改一条记录时，都需要先把对应的 undo日志 记下来，我们会为这些日志编号，也叫 undo no</p>
<p>undo 日志是存储在系统表空间中或一种专门存放 undo日志 的表空间（undo tablespace） </p>
<h3 id="1、INSERT操作对应的undo日志"><a href="#1、INSERT操作对应的undo日志" class="headerlink" title="1、INSERT操作对应的undo日志"></a>1、INSERT操作对应的undo日志</h3><p>插入一条记录到一个数据页中。如果希望回滚这个插入操作，那么把这条记录删除就好了，也就是说在写对应的 undo 日志时，主要是把这条记录的主键信息记上。插入操作对应类型为 <code>TRX_UNDO_INSERT_REC</code> 的 undo日志 ，它的完整结构如下图所示：  </p>
<img src="https://cdn.jsdelivr.net/gh/kuangtf/PictureBed/img/image-20220327103101108.png" alt="image-20220327103101108" style="zoom:80%;" />

<ul>
<li>undo no 在一个事务中是从 0 开始递增的，也就是说只要事务没提交，每生成一条 undo日志 ，那么该条日志的 undo no 就增1  </li>
<li>如果记录中的主键只包含一个列，那么在类型为 <code>TRX_UNDO_INSERT_REC</code> 的 undo日志 中只需要把该列占用的存储空间大小和真实值记录下来，如果记录中的主键包含多个列，那么每个列占用的存储空间大小和对应的真实值都需要记录下来（图中的 len 就代表列占用的存储空间大小， value 就代表列的真实值）</li>
</ul>
<p><strong>roll pointer隐藏列的含义</strong>  </p>
<p>rool pointer 隐藏列本质上就是一个指向记录对应的 undo日志 的一个指针  占用 7 个字节，存储到了类型为 <code>FIL_PAGE_INDEX</code> 的页面中（数据页），我们画一个图康康吧：</p>
<img src="https://cdn.jsdelivr.net/gh/kuangtf/PictureBed/img/image-20220327103124005.png" alt="image-20220327103124005" style="zoom:80%;" />

<h3 id="2、DELETE操作对应的undo日志"><a href="#2、DELETE操作对应的undo日志" class="headerlink" title="2、DELETE操作对应的undo日志"></a>2、DELETE操作对应的undo日志</h3><p>在讲解 undo日志之前，我们先来看看啥是「垃圾链表」，还是老习惯，上图：</p>
<img src="https://cdn.jsdelivr.net/gh/kuangtf/PictureBed/img/image-20220327103130103.png" alt="image-20220327103130103" style="zoom:80%;" />

<p>左侧为插入到页面中的记录会根据记录头信息中的 next_record 属性组成一个单向链表，我们把这个链表称之为「正常记录链表」；右侧是被删除的记录其实也会根据记录头信息中的 next_record 属性组成一个链表，只不过这个链表中的记录占用的存储空间可以被重新利用，所以也称这个链表为「垃圾链表」</p>
<p>Page Header 部分有一个称之为 <code>PAGE_FREE</code> 的属性，它指向由被删除记录组成的垃圾链表中的头节点     </p>
<p>当我们把正常记录链表中的记录删除时，经过下面两个阶段：</p>
<p>阶段一：仅仅将记录的 delete_mask 标识位设置为 1 ，其他的不做修改，称之为 「delete mark」  </p>
<p>注意：被删除的记录并没有被加入到垃圾链表，处于一个中间状态，如图：</p>
<p>​                  <img src="https://cdn.jsdelivr.net/gh/kuangtf/PictureBed/img/image-20220327103143279.png" alt="image-20220327103143279" style="zoom:80%;" />   </p>
<p>阶段二：当该删除语句所在的事务提交之后，会有专门的线程后来真正的把记录删除掉。所谓真正的删除就<br>是把该记录从 正常记录链表 中移除，并且加入到 垃圾链表 中，然后还要调整一些页面的其他信息，比如页面中的用户记录数量 <code>PAGE_N_RECS</code> 、上次插入记录的位置 <code>PAGE_LAST_INSERT</code> 、垃圾链表头节点的指针 <code>PAGE_FREE</code> 、页面中可重用的字节数量 <code>PAGE_GARBAGE</code> 、还有页目录的一些信息等等。这个阶段称之为 purge ，如图：</p>
<img src="https://cdn.jsdelivr.net/gh/kuangtf/PictureBed/img/image-20220327103156026.png" alt="image-20220327103156026" style="zoom:80%;" />

<p>将被删除记录加入到垃圾链表时，实际上加入到链表的头节点处，会跟着修改 <code>PAGE_FREE</code> 属性的值，只有阶段二执行完后这条记录就算是真正的被删除掉了  </p>
<p>在删除语句所在的事务提交之前，只会经历 阶段一，因此 InnoDB 设计了一种称之为<code>TRX_UNDO_DEL_MARK_REC</code>  类型的 undo日志，如图：</p>
<img src="https://cdn.jsdelivr.net/gh/kuangtf/PictureBed/img/image-20220327103202751.png" alt="image-20220327103202751" style="zoom:80%;" />

<p>现在我只想说 「what’s up」🙄🙄🙄</p>
<p>在对一条记录进行 delete mark 操作前，需要把该记录的旧的 trx_id 和 roll_pointer 隐藏列的值都给记<br>到对应的 undo日志 中来，就是我们图中显示的 old trx_id 和 old roll_pointer 属性，可以形成版本链    </p>
<p>TRX_UNDO_DEL_MARK_REC 的 undo 日志有一个 索引列各列信息 的内容，包括 pos，len，value，</p>
<p>pos是该列在记录中的位置，len 是该列在记录中的位置，value 该列实际值</p>
<p>其他的参数以后再介绍哈</p>
<h3 id="3、UPDATE-操作对应的-undo日志"><a href="#3、UPDATE-操作对应的-undo日志" class="headerlink" title="3、UPDATE 操作对应的 undo日志"></a>3、UPDATE 操作对应的 undo日志</h3><h4 id="1、不更新主键的情况"><a href="#1、不更新主键的情况" class="headerlink" title="1、不更新主键的情况"></a>1、不更新主键的情况</h4><p>就地更新（in-place update）：更新记录时，对于被更新的「每个列」来说，如果更新后的列和更新前的列占用的存储空间都一样大，那么就可以进行就地更新    </p>
<p>先删除掉旧记录，再插入新记录：在不更新主键的情况下，如果有任何一个被更新的列更新前和更新后占用的存储空间大小不一致，那么就需要先把这条旧的记录从聚簇索引页面中删除掉，然后再根据更新后列的值创建一条新的记录插入到页面中</p>
<p>注意：这个删除并不是 delete mark 操作，而是真正的删除掉，也就是把这条记录从 正常记录链表 中移除并加入到 垃圾链表 中    </p>
<p>这里如果新创建的记录占用的存储空间大小不超过旧记录占用的空间，那么可以直接重用被加入到 垃圾链<br>表 中的旧记录所占用的存储空间，否则的话需要在页面中新申请一段空间以供新记录使用，如果本页面内已经没有可用的空间的话，那就需要进行页面分裂操作，然后再插入新记录  </p>
<p>UPDATE 不更新主键的情况下undo日志类型为 <code>TRX_UNDO_UPD_EXIST_REC</code>，如图：</p>
 <img src="https://cdn.jsdelivr.net/gh/kuangtf/PictureBed/img/image-20220327103220220.png" alt="image-20220327103220220" style="zoom:80%;" />

<p>n_updated 属性表示本条 UPDATE 语句执行后将有几个列被更新，后边跟着的 <code>&lt;pos, old_len, old_value&gt;</code><br>分别表示被更新列在记录中的位置、更新前该列占用的存储空间大小、更新前该列的真实值。</p>
<p>如果在 UPDATE 语句中更新的列包含索引列，那么也会添加 索引列各列信息 这个部分，否则的话是不会添加这个部分的。  </p>
<h4 id="2、更新主键的情况"><a href="#2、更新主键的情况" class="headerlink" title="2、更新主键的情况"></a>2、更新主键的情况</h4><p>分为两步：</p>
<p>将旧记录进行 delete mark 操作  </p>
<blockquote>
<p> 小贴士：这里是 delete mark 操作，对旧记录只做一个 delete mark 操作，在事务提交后才由专门的线程做purge 操作，把它加入到垃圾链表中  </p>
</blockquote>
<p>根据更新后各列的值创建一条新记录，并将其插入到聚簇索引中，由于更新后的记录主键值发生了改变，所以需要重新从聚簇索引中定位这条记录所在的位置，然后把它插进去。  </p>
<h2 id="四、通用链表结构"><a href="#四、通用链表结构" class="headerlink" title="四、通用链表结构"></a>四、通用链表结构</h2><p>List Node 结构示意图：</p>
<img src="https://cdn.jsdelivr.net/gh/kuangtf/PictureBed/img/image-20220327103231146.png" alt="image-20220327103231146" style="zoom:80%;" />

<p>List Base Node 结构示意图：</p>
<p><img src="https://cdn.jsdelivr.net/gh/kuangtf/PictureBed/img/image-20220327103238754.png" alt="image-20220327103238754"></p>
<ul>
<li>List Length 表明该链表一共有多少节点</li>
</ul>
<p>List Node 与 List Base Node 的关系为：</p>
<img src="https://cdn.jsdelivr.net/gh/kuangtf/PictureBed/img/image-20220327103244068.png" alt="image-20220327103244068" style="zoom:80%;" />

<h2 id="五、FIL-PAGE-UNDO-LOG页面"><a href="#五、FIL-PAGE-UNDO-LOG页面" class="headerlink" title="五、FIL_PAGE_UNDO_LOG页面"></a>五、FIL_PAGE_UNDO_LOG页面</h2><p>FIL_PAGE_UNDO_LOG 类型的页面是专门用来存储 undo日志 的，结构如下：</p>
<img src="https://cdn.jsdelivr.net/gh/kuangtf/PictureBed/img/image-20220327103250246.png" alt="image-20220327103250246" style="zoom:80%;" />

<p>这里重点说下一下 Uodo Page Header，结构如下：</p>
<img src="https://cdn.jsdelivr.net/gh/kuangtf/PictureBed/img/image-20220327103258257.png" alt="image-20220327103258257" style="zoom:80%;" />

<ul>
<li><code>TRX_UNDO_PAGE_TYPE</code> ：本页面准备存储什么种类的 undo日志 。  <ul>
<li> <code>RX_UNDO_INSERT</code>：<code>TRX_UNDO_INSERT_REC</code> 属于它   </li>
<li><code>RX_UNDO_INSERT</code> ：，除了类型为 <code>TRX_UNDO_INSERT_REC</code> 的 undo日志 ，其他类型<br>  的 undo日志 都属于这个大类，比如我们前边说的 <code>TRX_UNDO_DEL_MARK_REC</code> 、<br>  <code>TRX_UNDO_UPD_EXIST_REC</code> 啥的，一般由 DELETE 、 UPDATE 语句产生的 undo日志 属于这个大类。  </li>
</ul>
</li>
<li><code>TRX_UNDO_PAGE_START</code> ：表示在当前页面中是从什么位置开始存储 undo日志 的，或者说表示第一条 undo日志 在本页面中的起始偏移量  </li>
<li><code>TRX_UNDO_PAGE_FREE</code> ：表示当前页面中存储的最后一条 undo 日志结束时的偏移量，或者说从这个位置开始，可以继续写入新的 undo日志 。  </li>
<li><code>RX_UNDO_PAGE_NODE</code> ：代表一个 List Node 结构  </li>
</ul>
<h2 id="六、Undo页面链表"><a href="#六、Undo页面链表" class="headerlink" title="六、Undo页面链表"></a>六、Undo页面链表</h2><h3 id="1、多个事务中的Undo页面链表"><a href="#1、多个事务中的Undo页面链表" class="headerlink" title="1、多个事务中的Undo页面链表"></a>1、多个事务中的Undo页面链表</h3><p>因为一个事务可能包含多个语句，可能产生很多 undo日志 ，这些日志可能一个页面放不下，需要放到多个页面中，这些页面就通 TRX_UNDO_PAGE_NODE 属性连成了链表：  </p>
<img src="https://cdn.jsdelivr.net/gh/kuangtf/PictureBed/img/image-20220327103305160.png" alt="image-20220327103305160" style="zoom:80%;" />

<p>我们之前说过，Undo 页面分为不同的类型，但是一个事务可能产生     2 种类型的 Undo页面，一个称之为 insert undo链表 ，另一个称之为 update undo链表 ，画个示意图吧：</p>
<img src="https://cdn.jsdelivr.net/gh/kuangtf/PictureBed/img/image-20220327103310223.png" alt="image-20220327103310223" style="zoom:80%;" />

<h3 id="2、多个事务中的Undo页面链表"><a href="#2、多个事务中的Undo页面链表" class="headerlink" title="2、多个事务中的Undo页面链表"></a>2、多个事务中的Undo页面链表</h3><p>为了尽可能提高 undo日志 的写入效率，不同事务执行过程中产生的 undo日志需要被写入到不同的 Undo页面链表中，还是画个图吧：画图画吐了（谁叫我这么喜欢你们呢😘😘😘)</p>
<img src="https://cdn.jsdelivr.net/gh/kuangtf/PictureBed/img/image-20220327103315461.png" alt="image-20220327103315461" style="zoom:80%;" />

<h2 id="七、undo日志具体写入过程"><a href="#七、undo日志具体写入过程" class="headerlink" title="七、undo日志具体写入过程"></a>七、undo日志具体写入过程</h2><h3 id="1、Undo-Log-Segment-Header"><a href="#1、Undo-Log-Segment-Header" class="headerlink" title="1、Undo Log Segment Header"></a>1、Undo Log Segment Header</h3><p>InnoDB 的规定，每一个 Undo页面链表都对应着一个段 ，称之为 Undo Log Segment 。也就是说链表中的页面都是从这个段里边申请的，所以他们在 Undo页面 链表的第一个页面，也就是上边提到的 first undo page 中设计了一个称之为 Undo Log Segment Header 的部分，这个部分中包含了该链表对应的段的 segment header 信息以及其他的一些关于这个段的信息，所以 Undo 页面链表的第一个页面其实长这样：</p>
<img src="https://cdn.jsdelivr.net/gh/kuangtf/PictureBed/img/image-20220327103326214.png" alt="image-20220327103326214" style="zoom:80%;" />  

<p>Undo Log Segment Header的结构如下：</p>
<img src="https://cdn.jsdelivr.net/gh/kuangtf/PictureBed/img/image-20220327103334449.png" alt="image-20220327103334449" style="zoom:80%;" />

<ul>
<li><code>TRX_UNDO_STATE</code> ：本 Undo页面 链表处在什么状态  <ul>
<li><code>TRX_UNDO_ACTIVE</code> ：活跃状态，也就是一个活跃的事务正在往这个段里边写入 undo日志 。</li>
<li><code>TRX_UNDO_CACHED</code> ：被缓存的状态。处在该状态的 Undo页面 链表等待着之后被其他事务重用。</li>
<li><code>TRX_UNDO_TO_FREE</code> ：对于 insert undo 链表来说，如果在它对应的事务提交之后，该链表不能被重用，那么就会处于这种状态。</li>
<li><code>TRX_UNDO_TO_PURGE</code> ：对于 update undo 链表来说，如果在它对应的事务提交之后，该链表不能被重用，那么就会处于这种状态。</li>
<li><code>TRX_UNDO_PREPARED</code> ：包含处于 PREPARE 阶段的事务产生的 undo日志 。  </li>
</ul>
</li>
<li><code>TRX_UNDO_LAST_LOG</code> ：本 Undo页面 链表中最后一个 Undo Log Header 的位置。  </li>
<li><code>TRX_UNDO_FSEG_HEADER</code> ：本 Undo页面 链表对应的段的 Segment Header 信息</li>
<li><code>TRX_UNDO_PAGE_LIST</code> ： Undo页面 链表的基节点  </li>
</ul>
<h3 id="2、Undo-Log-Header"><a href="#2、Undo-Log-Header" class="headerlink" title="2、Undo Log Header"></a>2、Undo Log Header</h3><p>讲了这么多页面的信息，大家可能会说，这有啥用啊，看了也记不住，好，听你的，以后少讲一点</p>
<p>那 Undo Log Header 有啥用呢，用来存储组的一些信息，那啥是组呢？同一个事务向一个 Undo页面 链表<br>中写入的 undo日志 算是一个组  </p>
<p>如图：</p>
<img src="https://cdn.jsdelivr.net/gh/kuangtf/PictureBed/img/image-20220327103403231.png" alt="image-20220327103403231" style="zoom:80%;" />

<p>Undo Log Header 具体的属性我实在是不想画了，太多了，感兴趣的同学自行去康康，老夫无能 🤣🤣🤣</p>
<p>小总结：对于没有被重用的 Undo页面 链表来说，链表的第一个页面，也就是 first undo page 在真正写入 undo日志前，会填充 Undo Page Header 、 Undo Log Segment Header 、 Undo Log Header 这3个部分，之后才开始正式写入 undo日志 。对于其他的页面来说，也就是 normal undo page 在真正写入 undo日志 前，只会填充 Undo Page Header 。链表的 List Base Node 存放到 first undo page 的 Undo Log Segment Header 部分， List Node 信息存放到每一个 Undo页面 的 undo Page Header 部分，所以画一个 Undo页面 链表的示意图就是这样：  </p>
<img src="https://cdn.jsdelivr.net/gh/kuangtf/PictureBed/img/image-20220327103414383.png" alt="image-20220327103414383" style="zoom:80%;" />

<h2 id="八、回滚段"><a href="#八、回滚段" class="headerlink" title="八、回滚段"></a>八、回滚段</h2><h3 id="1、回滚段的概念"><a href="#1、回滚段的概念" class="headerlink" title="1、回滚段的概念"></a>1、回滚段的概念</h3><p>我们现在知道一个事务在执行过程中最多可以分配  4个 Undo页面 链表，在同一时刻不同事务拥有的 Undo页面 链表是不一样的，所以在同一时刻系统里其实可以有许许多多个 Undo页面 链表存在。为了更好的管理这些链表，InnoDB 设计了一个称之为 Rollback Segment Header 的页面，在这个页面中存放了各个 Undo页面 链表的 frist undo page 的页号 ，他们把这些页号称之为 undo slot，如图：</p>
 <img src="https://cdn.jsdelivr.net/gh/kuangtf/PictureBed/img/image-20220327103421651.png" alt="image-20220327103421651" style="zoom:80%;" />

<p>每一个 Rollback Segment Header 页面都对应着一个段，这个段就称为 Rollback Segment ，翻译过来就是 回滚段 。与我们之前介绍的各种段不同的是，这个 Rollback Segment 里其实只有一个页面  </p>
<p>又到了解释这些字段是啥意思的时候了，相信你们肯定也不想看了，其实我更加不想写了 😒😒😒</p>
<ul>
<li><code>TRX_RSEG_MAX_SIZE</code> ：本 Rollback Segment 中管理的所有 Undo页面 链表中的 Undo页面 数量之和的最大值。换句话说，本 Rollback Segment 中所有 Undo页面 链表中的 Undo页面 数量之和不能超过 <code>TRX_RSEG_MAX_SIZE</code> 代表的值。该属性的值默认为无限大，也就是我们想写多少 Undo页面 都可以  </li>
<li><code>TRX_RSEG_HISTORY_SIZE</code> ： History 链表占用的页面数量。</li>
<li><code>TRX_RSEG_HISTORY</code> ： History 链表的基节点。  </li>
<li><code>TRX_RSEG_FSEG_HEADER</code> ：本 Rollback Segment 对应的10字节大小的 Segment Header 结构，通过它可以找到本段对应的 INODE Entry 。  </li>
<li><code>TRX_RSEG_UNDO_SLOTS</code> ：各个Undo页面链表的 first undo page 的页号集合，也就是 undo slot 集合  </li>
</ul>
<h3 id="2、从回滚段中申请Undo页面链表"><a href="#2、从回滚段中申请Undo页面链表" class="headerlink" title="2、从回滚段中申请Undo页面链表"></a>2、从回滚段中申请Undo页面链表</h3><p>初始情况下，由于未向任何事务分配任何 Undo页面 链表，所以对于一个 Rollback Segment Header 页面来说，它的各个 undo slot 都被设置成了一个特殊的值： FIL_NULL （对应的十六进制就是 0xFFFFFFFF），表示该undo slot 不指向任何页面。  </p>
<p>随着时间的流逝，开始有事务需要分配 Undo页面 链表了，就从回滚段的第一个 undo slot 开始，看看该 undo slot 的值是不是 FIL_NULL </p>
<ul>
<li>如果是 FIL_NULL ，那么在表空间中新创建一个段（也就是 Undo Log Segment ），然后从段里申请一个页面作为 Undo页面 链表的 first undo page ，然后把该 undo slot 的值设置为刚刚申请的这个页面的地址，这样也就意味着这个 undo slot 被分配给了这个事务。</li>
<li>如果不是 FIL_NULL ，说明该 undo slot 已经指向了一个 undo链表 ，也就是说这个 undo slot 已经被别的事务占用了，那就跳到下一个 undo slot ，判断该 undo slot 的值是不是 FIL_NULL ，重复上边的步骤。  </li>
</ul>
<p>一个 Rollback Segment Header 页面中包含 1024 个 undo slot ，如果这 1024 个 undo slot 的值都不为<br>FIL_NULL ，这就意味着这 1024 个 undo slot 都已经名花有主（被分配给了某个事务），此时由于新事务无法再获得新的 Undo页面 链表，就会回滚这个事务并且给用户报错  </p>
<pre class="line-numbers language-sql" data-language="sql"><code class="language-sql">Too many active concurrent <span class="token keyword">transactions</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre>

<p>当一个事务提交时，它所占用的 undo slot 有两种命运：  </p>
<ul>
<li><p>如果该 undo slot 指向的 Undo页面 链表符合被重用的条件，该 undo slot 就处于被缓存的状态，改 Undo页面 链表的 TRX_UNDO_STATE 属性会设置为 TRX_UNDO_CACHED 。被缓存的 undo slot 都会被加入到一个链表，根据对应的 Undo页面 链表的类型不同，也会被加入到不同的链表：  </p>
<ul>
<li>如果对应的 Undo页面 链表是 insert undo链表 ，则该 undo slot 会被加入 insert undo cached 链表 </li>
<li>如果对应的 Undo页面 链表是 update undo链表 ，则该 undo slot 会被加入 update undo cached链表  </li>
</ul>
<p>  一个回滚段就对应着上述两个 cached 链表 ，如果有新事务要分配 undo slot 时，先从对应的 cached链表 中找。如果没有被缓存的 undo slot ，才会到回滚段的 Rollback Segment Header 页面中再去找。  </p>
</li>
<li><p>如果该 undo slot 指向的 Undo页面 链表不符合被重用的条件，那么针对该 undo slot 对应的 Undo页面 链表类型不同，也会有不同的处理：  </p>
<ul>
<li>如果对应的 Undo页面 链表是 insert undo链表 ，则该 Undo页面 链表的 <code>TRX_UNDO_STATE</code> 属性会被设置为 <code>TRX_UNDO_TO_FREE</code> ，之后该 Undo页面 链表对应的段会被释放掉，然后把该 undo slot 的值设置为 FIL_NULL  </li>
<li>如果对应的 Undo页面 链表是 update undo链表 ，则该 Undo页面 链表的 <code>TRX_UNDO_STATE</code> 属性会被设置为 <code>TRX_UNDO_TO_PRUGE</code> ，则会将该 undo slot 的值设置为 FIL_NULL ，然后将本次事务写入的一组 undo 日志放到所谓的 History链表 中</li>
</ul>
</li>
</ul>
<h3 id="3、多个回滚段"><a href="#3、多个回滚段" class="headerlink" title="3、多个回滚段"></a>3、多个回滚段</h3><p>我们说一个事务执行过程中最多分配 4 个 Undo页面 链表，而一个回滚段里只有 1024 个 undo slot ，很显然 undo slot 的数量有点不能够啊，于是 InnoDB 定义了 128 个回滚段，这些回滚段的地址存储在第 5 号页面的某个区域包含了128个8字节大小的格子：  </p>
<img src="https://cdn.jsdelivr.net/gh/kuangtf/PictureBed/img/image-20220327103430771.png" alt="image-20220327103430771" style="zoom:80%;" />

<p>每个8字节的格子的构造就像这样：</p>
<img src="https://cdn.jsdelivr.net/gh/kuangtf/PictureBed/img/image-20220327103437250.png" alt="image-20220327103437250" style="zoom:80%;" />

<ul>
<li>4字节大小的 Space ID ，代表一个表空间的ID。</li>
<li>4字节大小的 Page number ，代表一个页号  </li>
</ul>
<p>每个8字节大小的 格子 相当于一个指针，指向某个表空间中的某个页面，这些页面就是 Rollback Segment Header 。这里需要注意的一点事，要定位一个 Rollback Segment Header 还需要知道对应的表空间ID，这也就意味着不同的回滚段可能分布在不同的表空间中。  </p>
<p>如图：</p>
<img src="https://cdn.jsdelivr.net/gh/kuangtf/PictureBed/img/image-20220327103445278.png" alt="image-20220327103445278" style="zoom:80%;" />

<h3 id="4、为事务分配Undo页面链表详细过程"><a href="#4、为事务分配Undo页面链表详细过程" class="headerlink" title="4、为事务分配Undo页面链表详细过程"></a>4、为事务分配Undo页面链表详细过程</h3><p>事务执行过程中分配 Undo页面 链表时的完整过程：</p>
<ul>
<li><p> 事务在执行过程中对普通表的记录首次做改动之前，首先会到系统表空间的第 5 号页面中分配一个回滚段（其实就是获取一个 Rollback Segment Header 页面的地址）。一旦某个回滚段被分配给了这个事务，那么之后该事务中再对普通表的记录做改动时，就不会重复分配了。  </p>
</li>
<li><p> 在分配到回滚段后，首先看一下这个回滚段的两个 cached链表 有没有已经缓存了的 undo slot ，比如如果事务做的是 INSERT 操作，就去回滚段对应的 insert undo cached 链表 中看看有没有缓存的 undo slot ；如果事务做的是 DELETE 操作，就去回滚段对应的 update undo cached链表 中看看有没有缓存的 undo slot 。如果有缓存的 undo slot ，那么就把这个缓存的 undo slot 分配给该事务  </p>
</li>
<li><p> 如果没有缓存的 undo slot 可供分配，那么就要到 Rollback Segment Header 页面中找一个可用的 undo slot 分配给当前事务。  </p>
</li>
<li><p>找到可用的 undo slot 后，如果该 undo slot 是从 cached链表 中获取的，那么它对应的 Undo Log<br>  Segment 已经分配了，否则的话需要重新分配一个 Undo Log Segment ，然后从该 Undo Log Segment 中申请一个页面作为 Undo页面 链表的 first undo page  </p>
</li>
<li><p> 然后事务就可以把 undo日志 写入到上边申请的 Undo页面 链表了！  </p>
</li>
</ul>
<blockquote>
<p>小贴士：如果一个事务在执行过程中既对普通表的记录做了改动，又对临时表的记录做了改动，那么需要为这个记录分配2个回滚段。并发执行的不同事务其实也可以被分配相同的回滚段，只要分配不同的 undo slot 就可以了  </p>
</blockquote>
<p>到这里我们的 undo log 就讲完了，内容还是比较多的，希望大家慢慢消化吧 😝😝😝</p>
<blockquote>
<p>巨人的肩膀：</p>
<p><a target="_blank" rel="noopener" href="https://juejin.cn/book/6844733769996304392">https://juejin.cn/book/6844733769996304392</a></p>
</blockquote>

                
            </div>
            <hr/>

            

    <div class="reprint" id="reprint-statement">
        
            <div class="reprint__author">
                <span class="reprint-meta" style="font-weight: bold;">
                    <i class="fas fa-user">
                        文章作者:
                    </i>
                </span>
                <span class="reprint-info">
                    <a href="/about" rel="external nofollow noreferrer">Skyu</a>
                </span>
            </div>
            <div class="reprint__type">
                <span class="reprint-meta" style="font-weight: bold;">
                    <i class="fas fa-link">
                        文章链接:
                    </i>
                </span>
                <span class="reprint-info">
                    <a href="https://kuangty.gitee.io/2021/11/04/MySQL/%E5%86%B0%E5%86%B0%E8%A6%81%E6%88%91%E6%95%99%E5%A5%B9-undo-log%EF%BC%8C%E6%80%8E%E4%B9%88%E5%8A%9E/">https://kuangty.gitee.io/2021/11/04/MySQL/%E5%86%B0%E5%86%B0%E8%A6%81%E6%88%91%E6%95%99%E5%A5%B9-undo-log%EF%BC%8C%E6%80%8E%E4%B9%88%E5%8A%9E/</a>
                </span>
            </div>
            <div class="reprint__notice">
                <span class="reprint-meta" style="font-weight: bold;">
                    <i class="fas fa-copyright">
                        版权声明:
                    </i>
                </span>
                <span class="reprint-info">
                    本博客所有文章除特別声明外，均采用
                    <a href="https://creativecommons.org/licenses/by/4.0/deed.zh" rel="external nofollow noreferrer" target="_blank">CC BY 4.0</a>
                    许可协议。转载请注明来源
                    <a href="/about" target="_blank">Skyu</a>
                    !
                </span>
            </div>
        
    </div>

    <script async defer>
      document.addEventListener("copy", function (e) {
        let toastHTML = '<span>复制成功，请遵循本文的转载规则</span><button class="btn-flat toast-action" onclick="navToReprintStatement()" style="font-size: smaller">查看</a>';
        M.toast({html: toastHTML})
      });

      function navToReprintStatement() {
        $("html, body").animate({scrollTop: $("#reprint-statement").offset().top - 80}, 800);
      }
    </script>



            <div class="tag_share" style="display: block;">
                <div class="post-meta__tag-list" style="display: inline-block;">
                    
                        <div class="article-tag">
                            
                                <a href="/tags/undo-log/">
                                    <span class="chip bg-color">undo log</span>
                                </a>
                            
                        </div>
                    
                </div>
                <div class="post_share" style="zoom: 80%; width: fit-content; display: inline-block; float: right; margin: -0.15rem 0;">
                    <link rel="stylesheet" type="text/css" href="/libs/share/css/share.min.css">
<div id="article-share">

    
    <div class="social-share" data-sites="twitter,facebook,google,qq,qzone,wechat,weibo,douban,linkedin" data-wechat-qrcode-helper="<p>微信扫一扫即可分享！</p>"></div>
    <script src="/libs/share/js/social-share.min.js"></script>
    

    

</div>

                </div>
            </div>
            
                <style>
    #reward {
        margin: 40px 0;
        text-align: center;
    }

    #reward .reward-link {
        font-size: 1.4rem;
        line-height: 38px;
    }

    #reward .btn-floating:hover {
        box-shadow: 0 6px 12px rgba(0, 0, 0, 0.2), 0 5px 15px rgba(0, 0, 0, 0.2);
    }

    #rewardModal {
        width: 320px;
        height: 350px;
    }

    #rewardModal .reward-title {
        margin: 15px auto;
        padding-bottom: 5px;
    }

    #rewardModal .modal-content {
        padding: 10px;
    }

    #rewardModal .close {
        position: absolute;
        right: 15px;
        top: 15px;
        color: rgba(0, 0, 0, 0.5);
        font-size: 1.3rem;
        line-height: 20px;
        cursor: pointer;
    }

    #rewardModal .close:hover {
        color: #ef5350;
        transform: scale(1.3);
        -moz-transform:scale(1.3);
        -webkit-transform:scale(1.3);
        -o-transform:scale(1.3);
    }

    #rewardModal .reward-tabs {
        margin: 0 auto;
        width: 210px;
    }

    .reward-tabs .tabs {
        height: 38px;
        margin: 10px auto;
        padding-left: 0;
    }

    .reward-content ul {
        padding-left: 0 !important;
    }

    .reward-tabs .tabs .tab {
        height: 38px;
        line-height: 38px;
    }

    .reward-tabs .tab a {
        color: #fff;
        background-color: #ccc;
    }

    .reward-tabs .tab a:hover {
        background-color: #ccc;
        color: #fff;
    }

    .reward-tabs .wechat-tab .active {
        color: #fff !important;
        background-color: #22AB38 !important;
    }

    .reward-tabs .alipay-tab .active {
        color: #fff !important;
        background-color: #019FE8 !important;
    }

    .reward-tabs .reward-img {
        width: 210px;
        height: 210px;
    }
</style>

<div id="reward">
    <a href="#rewardModal" class="reward-link modal-trigger btn-floating btn-medium waves-effect waves-light red">赏</a>

    <!-- Modal Structure -->
    <div id="rewardModal" class="modal">
        <div class="modal-content">
            <a class="close modal-close"><i class="fas fa-times"></i></a>
            <h4 class="reward-title">你的赏识是我前进的动力</h4>
            <div class="reward-content">
                <div class="reward-tabs">
                    <ul class="tabs row">
                        <li class="tab col s6 alipay-tab waves-effect waves-light"><a href="#alipay">支付宝</a></li>
                        <li class="tab col s6 wechat-tab waves-effect waves-light"><a href="#wechat">微 信</a></li>
                    </ul>
                    <div id="alipay">
                        <img src="/medias/reward/alipay.jpg" class="reward-img" alt="支付宝打赏二维码">
                    </div>
                    <div id="wechat">
                        <img src="/medias/reward/wechat.png" class="reward-img" alt="微信打赏二维码">
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>

<script>
    $(function () {
        $('.tabs').tabs();
    });
</script>

            
        </div>
    </div>

    

    

    

    

    

    

    

    

    

<article id="prenext-posts" class="prev-next articles">
    <div class="row article-row">
        
        <div class="article col s12 m6" data-aos="fade-up">
            <div class="article-badge left-badge text-color">
                <i class="fas fa-chevron-left"></i>&nbsp;上一篇</div>
            <div class="card">
                <a href="/2021/11/04/Redis/%E5%93%A8%E5%85%B5%E6%9C%BA%E5%88%B6/">
                    <div class="card-image">
                        
                        
                        <img src="/medias/featureimages/6.jpg" class="responsive-img" alt="处在监控下的 Redis 们">
                        
                        <span class="card-title">处在监控下的 Redis 们</span>
                    </div>
                </a>
                <div class="card-content article-content">
                    <div class="summary block-with-text">
                        
                            
                        
                    </div>
                    <div class="publish-info">
                        <span class="publish-date">
                            <i class="far fa-clock fa-fw icon-date"></i>2021-11-04
                        </span>
                        <span class="publish-author">
                            
                            <i class="fas fa-bookmark fa-fw icon-category"></i>
                            
                            <a href="/categories/Redis/" class="post-category">
                                    Redis
                                </a>
                            
                            
                        </span>
                    </div>
                </div>
                
                <div class="card-action article-tags">
                    
                    <a href="/tags/Redis-%E5%93%A8%E5%85%B5/">
                        <span class="chip bg-color">Redis 哨兵</span>
                    </a>
                    
                </div>
                
            </div>
        </div>
        
        
        <div class="article col s12 m6" data-aos="fade-up">
            <div class="article-badge right-badge text-color">
                下一篇&nbsp;<i class="fas fa-chevron-right"></i>
            </div>
            <div class="card">
                <a href="/2021/11/04/Java-%E5%B9%B6%E5%8F%91/JMM/">
                    <div class="card-image">
                        
                        
                        <img src="/medias/featureimages/11.jpg" class="responsive-img" alt="学妹要我教她 JMM">
                        
                        <span class="card-title">学妹要我教她 JMM</span>
                    </div>
                </a>
                <div class="card-content article-content">
                    <div class="summary block-with-text">
                        
                            
                        
                    </div>
                    <div class="publish-info">
                            <span class="publish-date">
                                <i class="far fa-clock fa-fw icon-date"></i>2021-11-04
                            </span>
                        <span class="publish-author">
                            
                            <i class="fas fa-bookmark fa-fw icon-category"></i>
                            
                            <a href="/categories/Java-%E5%B9%B6%E5%8F%91/" class="post-category">
                                    Java 并发
                                </a>
                            
                            
                        </span>
                    </div>
                </div>
                
                <div class="card-action article-tags">
                    
                    <a href="/tags/JMM/">
                        <span class="chip bg-color">JMM</span>
                    </a>
                    
                </div>
                
            </div>
        </div>
        
    </div>
</article>

</div>



<!-- 代码块功能依赖 -->
<script type="text/javascript" src="/libs/codeBlock/codeBlockFuction.js"></script>

<!-- 代码语言 -->

<script type="text/javascript" src="/libs/codeBlock/codeLang.js"></script>


<!-- 代码块复制 -->

<script type="text/javascript" src="/libs/codeBlock/codeCopy.js"></script>


<!-- 代码块收缩 -->

<script type="text/javascript" src="/libs/codeBlock/codeShrink.js"></script>


    </div>
    <div id="toc-aside" class="expanded col l3 hide-on-med-and-down">
        <div class="toc-widget card" style="background-color: white;">
            <div class="toc-title"><i class="far fa-list-alt"></i>&nbsp;&nbsp;目录</div>
            <div id="toc-content"></div>
        </div>
    </div>
</div>

<!-- TOC 悬浮按钮. -->

<div id="floating-toc-btn" class="hide-on-med-and-down">
    <a class="btn-floating btn-large bg-color">
        <i class="fas fa-list-ul"></i>
    </a>
</div>


<script src="/libs/tocbot/tocbot.min.js"></script>
<script>
    $(function () {
        tocbot.init({
            tocSelector: '#toc-content',
            contentSelector: '#articleContent',
            headingsOffset: -($(window).height() * 0.4 - 45),
            collapseDepth: Number('0'),
            headingSelector: 'h1, h2, h3, h4'
        });

        // modify the toc link href to support Chinese.
        let i = 0;
        let tocHeading = 'toc-heading-';
        $('#toc-content a').each(function () {
            $(this).attr('href', '#' + tocHeading + (++i));
        });

        // modify the heading title id to support Chinese.
        i = 0;
        $('#articleContent').children('h1, h2, h3, h4').each(function () {
            $(this).attr('id', tocHeading + (++i));
        });

        // Set scroll toc fixed.
        let tocHeight = parseInt($(window).height() * 0.4 - 64);
        let $tocWidget = $('.toc-widget');
        $(window).scroll(function () {
            let scroll = $(window).scrollTop();
            /* add post toc fixed. */
            if (scroll > tocHeight) {
                $tocWidget.addClass('toc-fixed');
            } else {
                $tocWidget.removeClass('toc-fixed');
            }
        });

        
        /* 修复文章卡片 div 的宽度. */
        let fixPostCardWidth = function (srcId, targetId) {
            let srcDiv = $('#' + srcId);
            if (srcDiv.length === 0) {
                return;
            }

            let w = srcDiv.width();
            if (w >= 450) {
                w = w + 21;
            } else if (w >= 350 && w < 450) {
                w = w + 18;
            } else if (w >= 300 && w < 350) {
                w = w + 16;
            } else {
                w = w + 14;
            }
            $('#' + targetId).width(w);
        };

        // 切换TOC目录展开收缩的相关操作.
        const expandedClass = 'expanded';
        let $tocAside = $('#toc-aside');
        let $mainContent = $('#main-content');
        $('#floating-toc-btn .btn-floating').click(function () {
            if ($tocAside.hasClass(expandedClass)) {
                $tocAside.removeClass(expandedClass).hide();
                $mainContent.removeClass('l9');
            } else {
                $tocAside.addClass(expandedClass).show();
                $mainContent.addClass('l9');
            }
            fixPostCardWidth('artDetail', 'prenext-posts');
        });
        
    });
</script>

    

</main>




    <footer class="page-footer bg-color">
    
        <link rel="stylesheet" href="/libs/aplayer/APlayer.min.css">
<style>
    .aplayer .aplayer-lrc p {
        
        display: none;
        
        font-size: 12px;
        font-weight: 700;
        line-height: 16px !important;
    }

    .aplayer .aplayer-lrc p.aplayer-lrc-current {
        
        display: none;
        
        font-size: 15px;
        color: #42b983;
    }

    
    .aplayer.aplayer-fixed.aplayer-narrow .aplayer-body {
        left: -66px !important;
    }

    .aplayer.aplayer-fixed.aplayer-narrow .aplayer-body:hover {
        left: 0px !important;
    }

    
</style>
<div class="">
    
    <div class="row">
        <meting-js class="col l8 offset-l2 m10 offset-m1 s12"
                   server="netease"
                   type="playlist"
                   id="503838841"
                   fixed='true'
                   autoplay='false'
                   theme='#42b983'
                   loop='all'
                   order='random'
                   preload='auto'
                   volume='0.7'
                   list-folded='true'
        >
        </meting-js>
    </div>
</div>

<script src="/libs/aplayer/APlayer.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/meting@2/dist/Meting.min.js"></script>

    

    <div class="container row center-align"
         style="margin-bottom: 15px !important;">
        <div class="col s12 m8 l8 copy-right">
            Copyright&nbsp;&copy;
            
                <span id="year">2019-2022</span>
            
            <span id="year">2019</span>
            <a href="/about" target="_blank">Gtwff</a>
            |&nbsp;Powered by&nbsp;<a href="https://hexo.io/" target="_blank">Hexo</a>
            |&nbsp;Theme&nbsp;<a href="https://github.com/blinkfox/hexo-theme-matery" target="_blank">Matery</a>
            <br>
            
                &nbsp;<i class="fas fa-chart-area"></i>&nbsp;站点总字数:&nbsp;<span
                        class="white-color">577.4k</span>
            
            
            
                
            
            
                <span id="busuanzi_container_site_pv">
                &nbsp;|&nbsp;<i class="far fa-eye"></i>&nbsp;总访问量:&nbsp;
                    <span id="busuanzi_value_site_pv" class="white-color"></span>
            </span>
            
            
                <span id="busuanzi_container_site_uv">
                &nbsp;|&nbsp;<i class="fas fa-users"></i>&nbsp;总访问人数:&nbsp;
                    <span id="busuanzi_value_site_uv" class="white-color"></span>
            </span>
            
            <br>

            <!-- 运行天数提醒. -->
            
                <span id="sitetime"> Loading ...</span>
                <script>
                    var calcSiteTime = function () {
                        var seconds = 1000;
                        var minutes = seconds * 60;
                        var hours = minutes * 60;
                        var days = hours * 24;
                        var years = days * 365;
                        var today = new Date();
                        var startYear = "2019";
                        var startMonth = "6";
                        var startDate = "28";
                        var startHour = "0";
                        var startMinute = "0";
                        var startSecond = "0";
                        var todayYear = today.getFullYear();
                        var todayMonth = today.getMonth() + 1;
                        var todayDate = today.getDate();
                        var todayHour = today.getHours();
                        var todayMinute = today.getMinutes();
                        var todaySecond = today.getSeconds();
                        var t1 = Date.UTC(startYear, startMonth, startDate, startHour, startMinute, startSecond);
                        var t2 = Date.UTC(todayYear, todayMonth, todayDate, todayHour, todayMinute, todaySecond);
                        var diff = t2 - t1;
                        var diffYears = Math.floor(diff / years);
                        var diffDays = Math.floor((diff / days) - diffYears * 365);

                        // 区分是否有年份.
                        var language = 'zh-CN';
                        if (startYear === String(todayYear)) {
                            document.getElementById("year").innerHTML = todayYear;
                            var daysTip = 'This site has been running for ' + diffDays + ' days';
                            if (language === 'zh-CN') {
                                daysTip = '本站已运行 ' + diffDays + ' 天';
                            } else if (language === 'zh-HK') {
                                daysTip = '本站已運行 ' + diffDays + ' 天';
                            }
                            document.getElementById("sitetime").innerHTML = daysTip;
                        } else {
                            document.getElementById("year").innerHTML = startYear + " - " + todayYear;
                            var yearsAndDaysTip = 'This site has been running for ' + diffYears + ' years and '
                                + diffDays + ' days';
                            if (language === 'zh-CN') {
                                yearsAndDaysTip = '本站已运行 ' + diffYears + ' 年 ' + diffDays + ' 天';
                            } else if (language === 'zh-HK') {
                                yearsAndDaysTip = '本站已運行 ' + diffYears + ' 年 ' + diffDays + ' 天';
                            }
                            document.getElementById("sitetime").innerHTML = yearsAndDaysTip;
                        }
                    }

                    calcSiteTime();
                </script>
            
            <br>
            
        </div>
        <div class="col s12 m4 l4 social-link social-statis">
    <a href="https://github.com/kuangtianyu" class="tooltipped" target="_blank" data-tooltip="访问我的GitHub" data-position="top" data-delay="50">
        <i class="fab fa-github"></i>
    </a>



    <a href="mailto:1575235124@qq.com" class="tooltipped" target="_blank" data-tooltip="邮件联系我" data-position="top" data-delay="50">
        <i class="fas fa-envelope-open"></i>
    </a>







    <a href="tencent://AddContact/?fromId=50&fromSubId=1&subcmd=all&uin=1575235124" class="tooltipped" target="_blank" data-tooltip="QQ联系我: 1575235124" data-position="top" data-delay="50">
        <i class="fab fa-qq"></i>
    </a>





    <a href="https://www.zhihu.com/people/kuang-tian-yu-59" class="tooltipped" target="_blank" data-tooltip="关注我的知乎: https://www.zhihu.com/people/kuang-tian-yu-59" data-position="top" data-delay="50">
        <i class="fab fa-zhihu1">知</i>
    </a>



    <a href="/atom.xml" class="tooltipped" target="_blank" data-tooltip="RSS 订阅" data-position="top" data-delay="50">
        <i class="fas fa-rss"></i>
    </a>

</div>
    </div>
</footer>

<div class="progress-bar"></div>


    <!-- 搜索遮罩框 -->
<div id="searchModal" class="modal">
    <div class="modal-content">
        <div class="search-header">
            <span class="title"><i class="fas fa-search"></i>&nbsp;&nbsp;搜索</span>
            <input type="search" id="searchInput" name="s" placeholder="请输入搜索的关键字"
                   class="search-input">
        </div>
        <div id="searchResult"></div>
    </div>
</div>

<script type="text/javascript">
$(function () {
    var searchFunc = function (path, search_id, content_id) {
        'use strict';
        $.ajax({
            url: path,
            dataType: "xml",
            success: function (xmlResponse) {
                // get the contents from search data
                var datas = $("entry", xmlResponse).map(function () {
                    return {
                        title: $("title", this).text(),
                        content: $("content", this).text(),
                        url: $("url", this).text()
                    };
                }).get();
                var $input = document.getElementById(search_id);
                var $resultContent = document.getElementById(content_id);
                $input.addEventListener('input', function () {
                    var str = '<ul class=\"search-result-list\">';
                    var keywords = this.value.trim().toLowerCase().split(/[\s\-]+/);
                    $resultContent.innerHTML = "";
                    if (this.value.trim().length <= 0) {
                        return;
                    }
                    // perform local searching
                    datas.forEach(function (data) {
                        var isMatch = true;
                        var data_title = data.title.trim().toLowerCase();
                        var data_content = data.content.trim().replace(/<[^>]+>/g, "").toLowerCase();
                        var data_url = data.url;
                        data_url = data_url.indexOf('/') === 0 ? data.url : '/' + data_url;
                        var index_title = -1;
                        var index_content = -1;
                        var first_occur = -1;
                        // only match artiles with not empty titles and contents
                        if (data_title !== '' && data_content !== '') {
                            keywords.forEach(function (keyword, i) {
                                index_title = data_title.indexOf(keyword);
                                index_content = data_content.indexOf(keyword);
                                if (index_title < 0 && index_content < 0) {
                                    isMatch = false;
                                } else {
                                    if (index_content < 0) {
                                        index_content = 0;
                                    }
                                    if (i === 0) {
                                        first_occur = index_content;
                                    }
                                }
                            });
                        }
                        // show search results
                        if (isMatch) {
                            str += "<li><a href='" + data_url + "' class='search-result-title'>" + data_title + "</a>";
                            var content = data.content.trim().replace(/<[^>]+>/g, "");
                            if (first_occur >= 0) {
                                // cut out 100 characters
                                var start = first_occur - 20;
                                var end = first_occur + 80;
                                if (start < 0) {
                                    start = 0;
                                }
                                if (start === 0) {
                                    end = 100;
                                }
                                if (end > content.length) {
                                    end = content.length;
                                }
                                var match_content = content.substr(start, end);
                                // highlight all keywords
                                keywords.forEach(function (keyword) {
                                    var regS = new RegExp(keyword, "gi");
                                    match_content = match_content.replace(regS, "<em class=\"search-keyword\">" + keyword + "</em>");
                                });

                                str += "<p class=\"search-result\">" + match_content + "...</p>"
                            }
                            str += "</li>";
                        }
                    });
                    str += "</ul>";
                    $resultContent.innerHTML = str;
                });
            }
        });
    };

    searchFunc('/search.xml', 'searchInput', 'searchResult');
});
</script>

    <!-- 回到顶部按钮 -->
<div id="backTop" class="top-scroll">
    <a class="btn-floating btn-large waves-effect waves-light" href="#!">
        <i class="fas fa-arrow-up"></i>
    </a>
</div>


    <script src="/libs/materialize/materialize.min.js"></script>
    <script src="/libs/masonry/masonry.pkgd.min.js"></script>
    <script src="/libs/aos/aos.js"></script>
    <script src="/libs/scrollprogress/scrollProgress.min.js"></script>
    <script src="/libs/lightGallery/js/lightgallery-all.min.js"></script>
    <script src="/js/matery.js"></script>

    <!-- Baidu Analytics -->

    <!-- Baidu Push -->

<script>
    (function () {
        var bp = document.createElement('script');
        var curProtocol = window.location.protocol.split(':')[0];
        if (curProtocol === 'https') {
            bp.src = 'https://zz.bdstatic.com/linksubmit/push.js';
        } else {
            bp.src = 'http://push.zhanzhang.baidu.com/push.js';
        }
        var s = document.getElementsByTagName("script")[0];
        s.parentNode.insertBefore(bp, s);
    })();
</script>

    
    <script src="/libs/others/clicklove.js" async="async"></script>
    
    
    <script async src="/libs/others/busuanzi.pure.mini.js"></script>
    

    

    

    <!--腾讯兔小巢-->
    
    
    <script type="text/javascript" color="0,0,255"
        pointColor="0,0,255" opacity='0.7'
        zIndex="-1" count="99"
        src="/libs/background/canvas-nest.js"></script>
    

    
    
    <script type="text/javascript" size="150" alpha='0.6'
        zIndex="-1" src="/libs/background/ribbon-refresh.min.js" async="async"></script>
    

    
    <script type="text/javascript" src="/libs/background/ribbon-dynamic.js" async="async"></script>
    

    
    <script src="/libs/instantpage/instantpage.js" type="module"></script>
    

</body>

</html>
